package avicit.bdp.oss.utils.upload.utils.redis;

import avicit.bdp.oss.utils.upload.utils.common.CommUtil;
import avicit.bdp.oss.utils.upload.utils.common.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @金航数码科技有限责任公司
 * @作者：developer
 * @邮箱：developer@avic-digital.com
 * @创建时间： 2020-12-09
 * @类说明：RedisUtils
 * @修改记录：
 * @注意事项：
 * @主要功能：Redis操作公共类
 */
@Service
public class RedisUtils {
    private static final Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    /**
     * 自增主键ID，支持多线程
     */
    private Map<String, RedisAtomicLong> map = new HashMap<>();

    @Resource
    private RedisTemplate redisTemplate;

    private static RedisUtils redisUtils = new RedisUtils();

    /**
     * 获取指定键值
     *
     * @param key
     * @return
     */
    public byte[] getValue(final byte[] key) {
        byte[] result = (byte[]) redisTemplate.execute(new RedisCallback<byte[]>() {
            public byte[] doInRedis(RedisConnection jedis) throws DataAccessException {
                return jedis.get(key);
            }
        });

        return result;
    }

    /**
     * 设置指定键值
     *
     * @param key
     * @param value
     */
    public void setValue(final byte[] key, final byte[] value) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.set(key, value);
                return null;
            }
        });
    }

    /**
     * 设置指定键值、并携带过期时间
     * @param key
     * @param value
     * @param expireTime
     */
    public void setValueWithExpireTime(final byte[] key, final byte[] value, long expireTime) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.set(key, value);
                jedis.expire(key, expireTime);

                return null;
            }
        });
    }

    /**
     * 删除指定键
     *
     * @param key
     */
    public void delKey(final byte[] key) {
        redisTemplate.execute(new RedisCallback<Void>() {
            public Void doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.del(new byte[][]{key});
                return null;
            }
        });
    }

    /**
     * 返回有序集合中指定区间的成员列表
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<byte[]> zrangeByScore(final byte[] key, final long min, final long max) {
        Set<byte[]> sets = (Set<byte[]>) redisTemplate.execute(new RedisCallback<Set<byte[]>>() {
            @Override
            public Set<byte[]> doInRedis(RedisConnection jedis)
                    throws DataAccessException {
                return jedis.zRange(key, min, max);
            }
        });

        return sets;
    }

    /**
     * 设置指定键值
     *
     * @param key
     * @param field
     * @param value
     */
    public void zAdd(final byte[] key, final int field, final byte[] value) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.zAdd(key, field, value);
                return null;
            }
        });
    }

    /**
     * 删除有序集合中指定范围的键值对
     * @param key
     * @param start
     * @param stop
     */
    public void zRemRange(final byte[] key, final long start, final long stop) {
        redisTemplate.execute(new RedisCallback<Void>() {
            public Void doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.zRemRange(key, start, stop);
                return null;
            }
        });
    }

    /**
     * 删除指定key、value
     * @param key
     * @param value
     * @return
     */
    public void zRem(final byte[] key, final byte[] value) {
        redisTemplate.execute(new RedisCallback<Void>() {
            public Void doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.zRem(key, value);
                return null;
            }
        });
    }

    /**
     * 根据key获取Redis中Hash表所有field、value
     *
     * @param key
     * @return
     */
    public Map<byte[], byte[]> getHashAll(final byte[] key) {
        Map<byte[], byte[]> result = (Map<byte[], byte[]>) redisTemplate.execute(new RedisCallback<Map<byte[], byte[]>>() {
            public Map<byte[], byte[]> doInRedis(RedisConnection jedis) throws DataAccessException {
                return jedis.hGetAll(key);
            }
        });

        return result;
    }

    /**
     * 根据key、field获取Redis中指定value
     *
     * @param key
     * @param field
     * @return
     */
    public byte[] getHash(final byte[] key, final byte[] field) {
        byte[] result = (byte[]) redisTemplate.execute(new RedisCallback<byte[]>() {
            public byte[] doInRedis(RedisConnection jedis) throws DataAccessException {
                return jedis.hGet(key, field);
            }
        });

        return result;
    }

    /**
     * 设置Hash表指定key、field、value
     *
     * @param key
     * @param field
     * @param value
     */
    public void setHash(final byte[] key, final byte[] field, final byte[] value) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.hSet(key, field, value);
                return null;
            }
        });
    }

    /**
     * 删除hash表中指定key，field
     * @param key
     * @param field
     */
    public void delHash(final byte[] key, final byte[] field) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.hDel(key, field);
                return null;
            }
        });
    }

    /**
     * 设置过期时间
     *
     * @param key
     * @param time
     */
    public void setExpireTime(final byte[] key, long time) {
        redisTemplate.execute(new RedisCallback<Void>() {
            public Void doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.expire(key, time);
                return null;
            }
        });
    }

    /**
     * 产生自增ID，支持分布式、多线程
     * @param key
     * @return
     */
    public Integer generateId(String key) {
        // 增加后缀，防止taskInfoKey被重复使用，否则Redis上报key重复异常
        String suffix = "GENERATE_ID";
        String finalKey = key + suffix;
        try {
            RedisLock.getInstance().lock(Constant.OSS_GENEERATE_ID_LOCK_KEY);

            if (map.get(finalKey) == null) {
                RedisAtomicLong counter = new RedisAtomicLong(finalKey, redisTemplate.getConnectionFactory(), 0);

                map.put(finalKey, counter);
            }

            return (int)map.get(finalKey).incrementAndGet();
        } finally {
            RedisLock.getInstance().unlock(Constant.OSS_GENEERATE_ID_LOCK_KEY);
        }
    }

    /**
     * 更新value
     * @param key
     * @param field
     * @param value
     */
    public void updateValue(final byte[] key, final double field, final byte[] value, final long start,
                            final  long stop) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection jedis) throws DataAccessException {
                // step1:删除旧值
                jedis.zRemRange(key, start, stop);

                // step2:设置新值
                jedis.zAdd(key, field, value);

                return null;
            }
        });
    }

    /**
     * 更新value
     * @param key
     * @param oldValue
     * @param newValue
     */
    public void updateValue(final byte[] key, final byte[] oldValue, final byte[] newValue) {
        redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection jedis) throws DataAccessException {
                // step1:根据key,score删除旧值
                jedis.zRem(key, oldValue);

                // step2:设置新值
                jedis.zAdd(key, 1, newValue);

                return null;
            }
        });
    }

    /**
     * 插入列表头部
     * @param key
     * @param value
     */
    public void lpush(final String key, final String value) {
        this.redisTemplate.execute(new RedisCallback<Void>() {
            public Void doInRedis(RedisConnection jedis) throws DataAccessException {
                jedis.lPush(CommUtil.encode(key), CommUtil.encode(value));
                return null;
            }
        });
    }

    /**
     * 移除列表的最后一个元素
     * @param key
     * @return
     */
    public String rpop(final String key) {
        return (String) this.redisTemplate.execute(new RedisCallback<String>() {
            public String doInRedis(RedisConnection jedis) throws DataAccessException {
                byte[] bytes = jedis.rPop(CommUtil.encode(key));
                return CommUtil.encode(bytes);
            }
        });
    }

    /**
     * 获取列表长度
     * @param key
     * @return
     */
    public long llen(final String key) {
        return ((Long) this.redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection jedis) throws DataAccessException {
                return jedis.lLen(CommUtil.encode(key));
            }
        })).longValue();
    }

    /**
     * 移除列表元素
     * @param key
     * @param count
     * @param value
     * @return
     */
    public long lRem(final String key, final Long count, final String value) {
        return ((Long) this.redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection jedis) throws DataAccessException {
                return jedis.lRem(CommUtil.encode(key), count, CommUtil.encode(value));
            }
        })).longValue();
    }
}
